package com.mdp.tpa.pay.wxpay.service;


import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.mdp.core.err.BizException;
import com.mdp.tpa.pay.cm.entity.Orders;
import com.mdp.tpa.pay.cm.service.OrdersService;
import com.mdp.tpa.pay.wxpay.config.WxpayConfig;
import com.mdp.tpa.pay.wxpay.enums.WxApiType;
import com.mdp.tpa.pay.wxpay.enums.WxNotifyType;
import com.mdp.tpa.pay.wxpay.enums.WxTradeState;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Service
public class WxpayNativeService {

    @Resource
    private WxpayConfig wxPayConfig;

    @Autowired
    private OrdersService ordersService;

    @Autowired
    RedisCacheLock redisCacheLock;


    private Logger log= LoggerFactory.getLogger(WxpayNativeService.class);


    /**
     * 创建订单，调用Native支付接口
     * @param orders
     * @return code_url 和 订单号
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> nativePay(Orders orders) throws Exception {

        log.info("订单信息拼接进请求参数，调用统一下单API");

        //调用统一下单API
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));

        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("appid", wxPayConfig.getAppid());//应用ID
        paramsMap.put("mchid", wxPayConfig.getMchId());//直连商户号
        paramsMap.put("description", orders.getName());//商品描述
        paramsMap.put("out_trade_no", orders.getPayId());//商户订单号
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));

        Map amountMap = new HashMap();//订单金额

        BigDecimal actualPrice = orders.getPayAt();
        int total = (actualPrice.multiply(new BigDecimal(100))).intValue();
//        int total = 1;
//        amountMap.put("total", total);//金额,注意此处为单位为分，不是元
        amountMap.put("total", total);
        amountMap.put("currency", "CNY");//货币类型
        paramsMap.put("amount", amountMap);
        JSONObject attach=new JSONObject();
        attach.put("otype",orders.getOtype());
        attach.put("payId", orders.getPayId());
        attach.put("orderId", orders.getId());
        paramsMap.put("attach", attach.toJSONString());

        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数 ===> {}" + jsonParams);

        StringEntity entity = new StringEntity(jsonParams,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayConfig.getWxPayClient().execute(httpPost);

        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                log.info("Native下单 成功, 返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("成功");
            } else {
                log.info("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
                throw new BizException("request failed，请求微信支付服务器出错,"+ bodyAsString);
            }

            //响应结果
            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
            //二维码链接
            String codeUrl = resultMap.get("code_url");

            //更新订单预支付生成时间
            /**
            if(StringUtils.hasText(codeUrl)){
                Orders ordersUpdate=new Orders(orders.getId());
                ordersUpdate.setPrepayTime(new Date());
                ordersUpdate.setOtype(orders.getOtype());
                this.ordersService.updatePrepayId(ordersUpdate);
            }
            **/
            //返回二维码
            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orders.getId());
            map.put("payId", orders.getPayId());
            return map;

        } finally {
            response.close();
        }
    }


    public static void main(String[] args) {

        Logger log= LoggerFactory.getLogger(WxpayNativeService.class);
        String plainText="{\"mchid\":\"1389170202\",\"appid\":\"wx9f0f033214e62ae8\",\"out_trade_no\":\"IN1670144539126128\",\"transaction_id\":\"4200001705202212042175443671\",\"trade_type\":\"NATIVE\",\"trade_state\":\"SUCCESS\",\"trade_state_desc\":\"支付成功\",\"bank_type\":\"PAB_CREDIT\",\"attach\":\"{\\\"otype\\\":\\\"2\\\",\\\"payId\\\":\\\"46c20de3-cb43-4f47-9810-8eae142361a5\\\"}\",\"success_time\":\"2022-12-04T17:02:39+08:00\",\"payer\":{\"openid\":\"ot4D26ZLdHbAXC6pWYqKGJVSuk6U\"},\"amount\":{\"total\":1,\"payer_total\":1,\"currency\":\"CNY\",\"payer_currency\":\"CNY\"}}";
        //将明文转换成map
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String tradeState = (String) plainTextMap.get("trade_state");
        String tranId= (String) plainTextMap.get("transaction_id");
        //附加参数
        String attachStr = (String) plainTextMap.get("attach");
        JSONObject attach=JSONObject.parseObject(attachStr);
        String otype= (String) attach.get("otype");
        String payId= (String) attach.get("payId");
        log.info("attach中的payId==>{}",payId);

    }

    /**
     * 根据微信支付返回通知进行处理，此处要注意并发、分布式问题——加锁
     *
     * {"mchid":"1389170202","appid":"wx9f0f033214e62ae8","out_trade_no":"IN1670139913531138","transaction_id":"4200001684202212043000412671","trade_type":"NATIVE","trade_state":"SUCCESS","trade_state_desc":"支付成功","bank_type":"PAB_CREDIT","attach":"{\"payId\":\"230b2677-99a0-47cb-8a08-78beb7a88496\"}","success_time":"2022-12-04T15:45:58+08:00","payer":{"openid":"ot4D26ZLdHbAXC6pWYqKGJVSuk6U"},"amount":{"total":1,"payer_total":1,"currency":"CNY","payer_currency":"CNY"}}
     * @param bodyMap
     * @throws GeneralSecurityException
     */
    @Transactional(rollbackFor = Exception.class)
    public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("微信支付返回支付结果，处理订单");

        //解密报文
        String plainText = decryptFromResource(bodyMap);

        //将明文转换成map
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String tradeState = (String) plainTextMap.get("trade_state");
        String tranId= (String) plainTextMap.get("transaction_id");
        //附加参数
        String attachStr = (String) plainTextMap.get("attach");
        JSONObject attach=JSONObject.parseObject(attachStr);
        String otype= (String) attach.get("otype");
        String payId= (String) attach.get("payId");
        if(!WxTradeState.SUCCESS.getType().equals(tradeState)){
            //非支付成功通知，直接返回
            return;
        }
        //获取订单金额——此处可能会使用如银行优惠券等，此时payer_total不等于我们系统里的订单金额
        Map<String,Object> amount = (Map) plainTextMap.get("amount");
        int total =((Double) amount.get("total")).intValue();//此处返回的也是分，1元返回100，实际支付金额（payer_total）+各种券=订单金额（total）
        BigDecimal actualAmount = new BigDecimal(total).divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP);

        //获取订单id
        //String orderId = (String)plainTextMap.get("out_trade_no");//订单后台修改价格会报订单号重复，因此用payId当做订单编号提交给微信

        String orderId= (String) attach.get("orderId");
        /*在对业务数据进行状态检查和处理之前，要采用数据锁进行并发控制，以避免函数重入造成的数据混乱*/
        //处理重复的通知
        String key = "wzsc" + "WxPayService" + "processOrder" + "WxPayTask" + orderId;//项目名+类名+方法名+订单id，与WxPayTask共享key，防止重复处理同一个订单
        if(redisCacheLock.containKey(key)){
            //无需处理
            return;
        }else{
            redisCacheLock.setKeyValueTimeOut(key,key,5L);//设置过期时间为5分钟
            if(!"5".equals(otype)){
                Orders queryOrders = ordersService.getOrdersById(otype, orderId);
                if(!payId.equals(queryOrders.getPayId())){
                    throw new BizException("payId-err","付款流水号不正确");
                }
            }

            //处理支付成功逻辑——更新订单状态、记录支付日志
            try {
                this.ordersService.payConfirm(otype, orderId,payId,tranId, actualAmount,"");
            }catch (BizException e) {
                log.error("",e);
                throw e;
            } catch (Exception e) {
                log.error("微信支付回调业务处理报错,plainTextMap:" + plainTextMap);
                log.error("",e);
                throw e;
            }
        }
    }

    public String queryOrderFromWx(String payId) throws Exception {

        log.info("查单接口调用 ===> {}", payId);

        String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), payId);
        url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());

        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayConfig.getWxPayClient().execute(httpGet);

        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                log.info("成功, 返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("成功");
            } else {
                log.info("查单接口调用,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }

            return bodyAsString;

        } finally {
            response.close();
        }

    }

    /**
     * 根据订单号查询微信支付查单接口，核实订单状态
     * 如果订单已支付，则更新商户端订单状态，并记录支付日志
     * @param order
     */
    @Transactional(rollbackFor = Exception.class)
    public String checkOrderStatus(Orders order) throws Exception {
        //调用微信支付查单接口
        String result = this.queryOrderFromWx(order.getPayId());

        Gson gson = new Gson();
        Map<String, Object> resultMap = gson.fromJson(result, HashMap.class);

        //获取微信支付端的订单状态
        String tradeState = (String) resultMap.get("trade_state");

        //判断订单状态
        if(WxTradeState.SUCCESS.getType().equals(tradeState)){
            log.warn("核实订单已支付 ===> {}", order.getId());
            //如果确认订单已支付则更新本地订单状态
            Map<String,Object> amount = (Map) resultMap.get("amount");
            int total =((Double) amount.get("total")).intValue();//此处返回的是分
            BigDecimal actualAmount = new BigDecimal(total).divide(new BigDecimal(100));
//            this.ordersService.payConfirm(order.getOtype(), order.getId(), actualAmount, (String) resultMap.get("transaction_id"),"1");
        }

        return tradeState;
    }

    /**
     * 对称解密
     * @param bodyMap
     * @return
     */
    private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {

        log.info("密文解密");

        //通知数据
        Map<String, String> resourceMap = (Map) bodyMap.get("resource");
        //数据密文
        String ciphertext = resourceMap.get("ciphertext");
        //随机串
        String nonce = resourceMap.get("nonce");
        //附加数据
        String associatedData = resourceMap.get("associated_data");

        log.info("密文 ===> {}", ciphertext);
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);

        log.info("明文 ===> {}", plainText);

        return plainText;
    }



}
